#include "uevent.h"
#include <sys/select.h>
#include <unistd.h>
#include <assert.h>
#include "string.h"
#include "inner_types.h"
#include <errno.h>
#include <stdio.h>

static void _Wakeup(UEventLoop *loop);

#include "file_desc.c"
#include "timer.c"

UEventRetCode UEvent_InitLoop(UEventLoop *loop) {
    if (loop == NULL)
        return kUEventInvalidArgument;

    memset(loop, 0, sizeof(UEventLoop));

    loop->in_loop = false;

    int fds[2] = {-1, -1};
    int status = pipe(fds);
    if (status != 0) {
        return kUEventCreatePipeFail;
    }
    loop->keep_running = false;
    loop->selecting = false;

    loop->read_fd  = fds[0];
    loop->write_fd = fds[1];

    loop->executing_fd_callback = false;
    loop->file_descs = PtrVector_Create(0);
    loop->file_descs_tobe = PtrVector_Create(0);

    loop->executing_timer_callback = false;
    INIT_LIST_HEAD(&loop->timer_list);

    return kUEventOK;
}

UEventRetCode UEvent_ReleaseLoop(UEventLoop *loop) {
    if (loop == NULL)
        return kUEventInvalidArgument;

    if (loop->in_loop)  //! 不可以释放正在等待的loop
        return kUEventDeleteRunningLoop;

    close(loop->read_fd);
    close(loop->write_fd);

    PtrVector_Destory(loop->file_descs, _FreeFunc_DeleteFileDesc);
    PtrVector_Destory(loop->file_descs_tobe, _FreeFunc_DeleteFileDesc);
    loop->file_descs = NULL;
    loop->file_descs_tobe = NULL;

    _DeleteAllTimerList(&loop->timer_list);

    return kUEventOK;
}

UEventLoop* UEvent_NewLoop() {
    UEventLoop *loop = (UEventLoop*)malloc(sizeof(UEventLoop));
    if (loop != NULL) {
        if (UEvent_InitLoop(loop) != kUEventOK) {
            free(loop);
            loop = NULL;
        }
    }

    return loop;
}

UEventRetCode UEvent_DeleteLoop(UEventLoop *loop) {
    if (loop == NULL)
        return kUEventInvalidArgument;

    UEventRetCode retcode = UEvent_ReleaseLoop(loop);
    if (retcode == kUEventOK)
        free(loop);

    return retcode;
}

static void _Wakeup(UEventLoop *loop) {
    if (loop->selecting) {
        char tmp = 'w';
        write(loop->write_fd, &tmp, 1);
    }
}

static bool _CondFuncIsFileDescDeleted(void *p, void *a) {
    FileDesc *file_desc = (FileDesc*)p;
    return file_desc->is_deleted;
}

static void _RemoveDeletedFileDescs(UEventLoop *loop) {
    PtrVector_RemoveIf(loop->file_descs,
                       _CondFuncIsFileDescDeleted, NULL,
                       _FreeFunc_DeleteFileDesc);
}

static void _AddNewFileDescs(UEventLoop *loop) {
    while (PtrVector_Size(loop->file_descs_tobe) > 0) {
        FileDesc *file_desc = (FileDesc*)PtrVector_PopBack(loop->file_descs_tobe);
        PtrVector_PushBack(loop->file_descs, file_desc);
    }
}

static void _FillFDSetBeforeWait(UEventLoop *loop,
                                 fd_set *read_set,
                                 fd_set *write_set,
                                 fd_set *except_set,
                                 int *highest_fd) {
    for (int i = 0; i < PtrVector_Size(loop->file_descs); ++i) {
        FileDesc *file_desc = (FileDesc*) PtrVector_GetAt(loop->file_descs, i);

        if (file_desc->detail.operation_bits & kUEventOperationRead)
            FD_SET(file_desc->fd, read_set);

        if (file_desc->detail.operation_bits & kUEventOperationWrite)
            FD_SET(file_desc->fd, write_set);

        if (file_desc->detail.operation_bits & kUEventOperationExcept)
            FD_SET(file_desc->fd, except_set);

        if (*highest_fd < file_desc->fd)
            *highest_fd = file_desc->fd;
    }
}

static void _CheckFDSetAfterWait(UEventLoop *loop,
                                 fd_set *read_set,
                                 fd_set *write_set,
                                 fd_set *except_set) {
    loop->executing_fd_callback = true;
    for (int i = 0; i < PtrVector_Size(loop->file_descs); ++i) {
        FileDesc *file_desc = (FileDesc*) PtrVector_GetAt(loop->file_descs, i);
        if (FD_ISSET(file_desc->fd, read_set))
            (file_desc->detail.callback_func)(file_desc->fd, kUEventOperationRead, file_desc->detail.callback_arg);

        if (FD_ISSET(file_desc->fd, write_set))
            (file_desc->detail.callback_func)(file_desc->fd, kUEventOperationWrite, file_desc->detail.callback_arg);

        if (FD_ISSET(file_desc->fd, except_set))
            (file_desc->detail.callback_func)(file_desc->fd, kUEventOperationExcept, file_desc->detail.callback_arg);
    }
    loop->executing_fd_callback = false;
}

UEventRetCode UEvent_RunLoop(UEventLoop *loop) {
    if (loop == NULL)
        return kUEventInvalidArgument;

    loop->keep_running = true;
    loop->in_loop = true;
    while (loop->keep_running) {
        fd_set read_set, write_set, except_set;

        FD_ZERO(&read_set);
        FD_ZERO(&write_set);
        FD_ZERO(&except_set);

        int highest_fd = loop->read_fd; //! loop自身有一个用于唤醒的fd
        FD_SET(loop->read_fd, &read_set);

        _AddNewFileDescs(loop);
        _RemoveDeletedFileDescs(loop);
        _FillFDSetBeforeWait(loop, &read_set, &write_set, &except_set, &highest_fd);

        uint32_t wait_time_ms = _GetWaitTimeMs(&loop->timer_list);
        struct timeval timeout_struct = { wait_time_ms / 1000, (wait_time_ms % 1000) * 1000 };

        loop->selecting = true;
        int select_ret = select((highest_fd + 1), &read_set, &write_set, &except_set,
                                (wait_time_ms == FOREVER_TIME_MS) ? NULL : &timeout_struct);
        loop->selecting = false;

        if (select_ret < 0) {
            if (errno != EINTR)
                return kUEventSelectUnexpectBreak;
        } else if (select_ret > 0) {
            _CheckFDSetAfterWait(loop, &read_set, &write_set, &except_set);
            if (FD_ISSET(loop->read_fd, &read_set)) {
                char tmp[32] = {0};
                read(loop->read_fd, tmp, sizeof(32));
            }
        }

        _CheckTimerAfterWait(loop);

    }
    loop->in_loop = false;

    return kUEventOK;
}

UEventRetCode UEvent_BreakLoop(UEventLoop *loop) {
    if (loop == NULL)
        return kUEventInvalidArgument;

    loop->keep_running = false;
    _Wakeup(loop);

    return kUEventOK;
}
